package com.ozo.cloud.common.core.exception.advice;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.dev33.satoken.exception.SaTokenException;
import com.ozo.cloud.common.base.constant.CommonConstant;
import com.ozo.cloud.common.base.exception.CommonExceptionEnum;
import com.ozo.cloud.common.base.model.ResultVO;
import com.ozo.cloud.common.core.exception.ServiceException;
import jakarta.servlet.ServletException;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.ValidationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.resource.NoResourceFoundException;

import java.util.List;
import java.util.Set;

/**
 * 全局错误处理器
 *
 * @author qiangesoft
 * @date 2024-06-28
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionAdvice {

    /**
     * 请求参数缺失
     *
     * <p>
     * 接口上设置了@RequestParam("xx")参数，结果并未传递xx
     */
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public ResultVO missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException e) {
        log.warn("[missingServletRequestParameterExceptionHandler]", e);

        return ResultVO.fail(CommonExceptionEnum.BAD_REQUEST.getCode(), String.format("请求参数缺失:%s", e.getParameterName()));
    }

    /**
     * 请求参数类型错误
     *
     * <p>
     * 接口上设置了@RequestParam("xx")参数为Integer，结果传递xx类型为String
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResultVO methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException e) {
        log.warn("[methodArgumentTypeMismatchExceptionHandler]", e);

        return ResultVO.fail(CommonExceptionEnum.BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", e.getMessage()));
    }

    /**
     * 参数校验异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
    public ResultVO methodArgumentNotValidExceptionHandler(Exception e) {
        log.error("[methodArgumentNotValidExceptionHandler]", e);

        List<FieldError> fieldErrors = null;
        // 参数校验不正确
        if (e instanceof MethodArgumentNotValidException) {
            fieldErrors = ((MethodArgumentNotValidException) e).getBindingResult().getFieldErrors();
        }
        // 参数绑定不正确
        if (e instanceof BindException) {
            fieldErrors = ((BindException) e).getBindingResult().getFieldErrors();
        }

        if (fieldErrors == null) {
            return ResultVO.fail(CommonExceptionEnum.BAD_REQUEST);
        }

        StringBuilder stringBuilder = new StringBuilder();
        for (FieldError fieldError : fieldErrors) {
            stringBuilder.append(fieldError.getField() + CommonConstant.COLON + fieldError.getDefaultMessage()).append(CommonConstant.SEMICOLON);
        }
        return ResultVO.fail(CommonExceptionEnum.BAD_REQUEST, stringBuilder.toString());
    }

    /**
     * 参数校验异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler({ConstraintViolationException.class, ValidationException.class})
    public ResultVO validationExceptionHandler(Exception e) {
        log.error("[validationExceptionHandler]", e);

        // Validator校验不通过产生的异常
        StringBuilder stringBuilder = new StringBuilder();
        if (e instanceof ConstraintViolationException) {
            Set<ConstraintViolation<?>> constraintViolations = ((ConstraintViolationException) e).getConstraintViolations();
            for (ConstraintViolation<?> constraintViolation : constraintViolations) {
                stringBuilder.append(constraintViolation.getMessage()).append(CommonConstant.SEMICOLON);
            }
        }
        // 本地参数校验时，抛出的ValidationException异常
        if (e instanceof ValidationException) {
            stringBuilder.append(e.getMessage());
        }

        return ResultVO.fail(CommonExceptionEnum.BAD_REQUEST, stringBuilder.toString());
    }

    /**
     * 请求地址不存在
     *
     * <p>
     * 注意，它需要设置如下两个配置项：
     * 1. spring.mvc.throw-exception-if-no-handler-found 为 true
     * 2. spring.mvc.static-path-pattern 为 /statics/**
     */
    @ExceptionHandler({NoHandlerFoundException.class, NoResourceFoundException.class})
    public ResultVO noHandlerFoundExceptionHandler(ServletException e) {
        log.warn("[noFoundExceptionHandler]", e);

        return ResultVO.fail(CommonExceptionEnum.NOT_FOUND);
    }

    /**
     * 请求方法不正确
     *
     * <p>
     * 接口的方法为GET方式，结果请求方法为POST方式，导致不匹配
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ResultVO httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException e) {
        log.warn("[httpRequestMethodNotSupportedExceptionHandler]", e);

        return ResultVO.fail(CommonExceptionEnum.METHOD_NOT_ALLOWED);
    }

    /**
     * 消息不可重复读异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler({HttpMessageNotReadableException.class})
    public ResultVO methodArgumentNotValidExceptionHandler(HttpMessageNotReadableException e) {
        log.error("[methodArgumentNotValidExceptionHandler]", e);

        return ResultVO.fail(CommonExceptionEnum.HTTP_MESSAGE_NOT_READABLE);
    }

    /**
     * 未登录异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(NotLoginException.class)
    public ResultVO notLoginExceptionHandler(NotLoginException e) {
        log.error("[notLoginExceptionHandler]", e);

        return ResultVO.fail(CommonExceptionEnum.UNAUTHORIZED, e.getMessage());
    }

    /**
     * 无权限异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler({NotRoleException.class, NotPermissionException.class})
    public ResultVO saTokenExceptionHandler(SaTokenException e) {
        log.error("[saTokenExceptionHandler]", e);

        return ResultVO.fail(CommonExceptionEnum.FORBIDDEN, e.getMessage());
    }

    /**
     * 业务异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(ServiceException.class)
    public ResultVO serviceExceptionHandler(ServiceException e) {
        log.error("[serviceExceptionHandler]", e);

        return ResultVO.fail(e.getCode(), e.getErrorMessage());
    }

    /**
     * 兜底异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(Throwable.class)
    public ResultVO exceptionHandler(Throwable e) {
        log.error("[exceptionHandler]", e);

        return ResultVO.fail(CommonExceptionEnum.INTERNAL_SERVER_ERROR);
    }

}
